/*
 * Copyright (c) 2014, Peter Thorson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the WebSocket++ Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef WEBSOCKETPP_PROCESSOR_BASE_HPP
#define WEBSOCKETPP_PROCESSOR_BASE_HPP

#include <websocketpp/close.hpp>
#include <websocketpp/utilities.hpp>
#include <websocketpp/uri.hpp>

#include <websocketpp/common/cpp11.hpp>
#include <websocketpp/common/system_error.hpp>

#include <string>

namespace websocketpp
{
namespace processor
{

/// Constants related to processing WebSocket connections
namespace constants
{

static char const upgrade_token[] = "websocket";
static char const connection_token[] = "upgrade";
static char const handshake_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

} // namespace constants


/// Processor class related error codes
namespace error_cat
{
enum value {
  BAD_REQUEST = 0, // Error was the result of improperly formatted user input
  INTERNAL_ERROR = 1, // Error was a logic error internal to WebSocket++
  PROTOCOL_VIOLATION = 2,
  MESSAGE_TOO_BIG = 3,
  PAYLOAD_VIOLATION = 4 // Error was due to receiving invalid payload data
};
} // namespace error_cat

/// Error code category and codes used by all processor types
namespace error
{
enum processor_errors {
  /// Catch-all error for processor policy errors that don't fit in other
  /// categories
  general = 1,

  /// Error was the result of improperly formatted user input
  bad_request,

  /// Processor encountered a protocol violation in an incoming message
  protocol_violation,

  /// Processor encountered a message that was too large
  message_too_big,

  /// Processor encountered invalid payload data.
  invalid_payload,

  /// The processor method was called with invalid arguments
  invalid_arguments,

  /// Opcode was invalid for requested operation
  invalid_opcode,

  /// Control frame too large
  control_too_big,

  /// Illegal use of reserved bit
  invalid_rsv_bit,

  /// Fragmented control message
  fragmented_control,

  /// Continuation without message
  invalid_continuation,

  /// Clients may not send unmasked frames
  masking_required,

  /// Servers may not send masked frames
  masking_forbidden,

  /// Payload length not minimally encoded
  non_minimal_encoding,

  /// Not supported on 32 bit systems
  requires_64bit,

  /// Invalid UTF-8 encoding
  invalid_utf8,

  /// Operation required not implemented functionality
  not_implemented,

  /// Invalid HTTP method
  invalid_http_method,

  /// Invalid HTTP version
  invalid_http_version,

  /// Invalid HTTP status
  invalid_http_status,

  /// Missing Required Header
  missing_required_header,

  /// Embedded SHA-1 library error
  sha1_library,

  /// No support for this feature in this protocol version.
  no_protocol_support,

  /// Reserved close code used
  reserved_close_code,

  /// Invalid close code used
  invalid_close_code,

  /// Using a reason requires a close code
  reason_requires_code,

  /// Error parsing subprotocols
  subprotocol_parse_error,

  /// Error parsing extensions
  extension_parse_error,

  /// Extension related operation was ignored because extensions are disabled
  extensions_disabled,

  /// Short Ke3 read. Hybi00 requires a third key to be read from the 8 bytes
  /// after the handshake. Less than 8 bytes were read.
  short_key3
};

/// Category for processor errors
class processor_category : public lib::error_category
{
public:
  processor_category() {}

  char const *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_
  {
    return "websocketpp.processor";
  }

  std::string message (int value) const
  {
    switch (value) {
    case error::general:
      return "Generic processor error";

    case error::bad_request:
      return "invalid user input";

    case error::protocol_violation:
      return "Generic protocol violation";

    case error::message_too_big:
      return "A message was too large";

    case error::invalid_payload:
      return "A payload contained invalid data";

    case error::invalid_arguments:
      return "invalid function arguments";

    case error::invalid_opcode:
      return "invalid opcode";

    case error::control_too_big:
      return "Control messages are limited to fewer than 125 characters";

    case error::invalid_rsv_bit:
      return "Invalid use of reserved bits";

    case error::fragmented_control:
      return "Control messages cannot be fragmented";

    case error::invalid_continuation:
      return "Invalid message continuation";

    case error::masking_required:
      return "Clients may not send unmasked frames";

    case error::masking_forbidden:
      return "Servers may not send masked frames";

    case error::non_minimal_encoding:
      return "Payload length was not minimally encoded";

    case error::requires_64bit:
      return "64 bit frames are not supported on 32 bit systems";

    case error::invalid_utf8:
      return "Invalid UTF8 encoding";

    case error::not_implemented:
      return "Operation required not implemented functionality";

    case error::invalid_http_method:
      return "Invalid HTTP method.";

    case error::invalid_http_version:
      return "Invalid HTTP version.";

    case error::invalid_http_status:
      return "Invalid HTTP status.";

    case error::missing_required_header:
      return "A required HTTP header is missing";

    case error::sha1_library:
      return "SHA-1 library error";

    case error::no_protocol_support:
      return "The WebSocket protocol version in use does not support this feature";

    case error::reserved_close_code:
      return "Reserved close code used";

    case error::invalid_close_code:
      return "Invalid close code used";

    case error::reason_requires_code:
      return "Using a close reason requires a valid close code";

    case error::subprotocol_parse_error:
      return "Error parsing subprotocol header";

    case error::extension_parse_error:
      return "Error parsing extension header";

    case error::extensions_disabled:
      return "Extensions are disabled";

    case error::short_key3:
      return "Short Hybi00 Key 3 read";

    default:
      return "Unknown";
    }
  }
};

/// Get a reference to a static copy of the processor error category
inline lib::error_category const &get_processor_category()
{
  static processor_category instance;
  return instance;
}

/// Create an error code with the given value and the processor category
inline lib::error_code make_error_code (error::processor_errors e)
{
  return lib::error_code (static_cast<int> (e), get_processor_category() );
}

/// Converts a processor error_code into a websocket close code
/**
 * Looks up the appropriate WebSocket close code that should be sent after an
 * error of this sort occurred.
 *
 * If the error is not in the processor category close::status::blank is
 * returned.
 *
 * If the error isn't normally associated with reasons to close a connection
 * (such as errors intended to be used internally or delivered to client
 * applications, ex: invalid arguments) then
 * close::status::internal_endpoint_error is returned.
 */
inline close::status::value to_ws (lib::error_code ec)
{
  if (ec.category() != get_processor_category() ) {
    return close::status::blank;
  }

  switch (ec.value() ) {
  case error::protocol_violation:
  case error::control_too_big:
  case error::invalid_opcode:
  case error::invalid_rsv_bit:
  case error::fragmented_control:
  case error::invalid_continuation:
  case error::masking_required:
  case error::masking_forbidden:
  case error::reserved_close_code:
  case error::invalid_close_code:
    return close::status::protocol_error;

  case error::invalid_payload:
  case error::invalid_utf8:
    return close::status::invalid_payload;

  case error::message_too_big:
    return close::status::message_too_big;

  default:
    return close::status::internal_endpoint_error;
  }
}

} // namespace error
} // namespace processor
} // namespace websocketpp

_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_
template<> struct
  is_error_code_enum<websocketpp::processor::error::processor_errors> {
  static bool const value = true;
};
_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_

#endif //WEBSOCKETPP_PROCESSOR_BASE_HPP
